跳到主要内容

C++ 36-37 智能指针分析

36 经典问题解析三

关于赋值的疑问

什么时候需要重载赋值操作符?编译器是否提供默认的赋值操作?

  • 编译器为每个类默认重载了赋值操作符
  • 默认的赋值操作符仅完成浅拷贝
  • 当需要进行深拷贝时必须重载赋值操作符
  • 赋值操作符与拷贝构造函数有相同的存在意义

编程实验:默认赋值操作符重载

#include <iostream>
#include <string>

using namespace std;

class Test {
int* m_pointer;
public:
Test() {
m_pointer = nullptr;
}
Test(int i) {
m_pointer = new int(i);
}
Test(const Test& obj) {
m_pointer = new int(*obj.m_pointer);
}

void print() {
cout << "m_pointer = " << hex << m_pointer << endl;
}
~Test() {
delete m_pointer;
}
};

int main() {
Test t1 = 1; // t1.m_pointer指向0xFF010203(举例)
Test t2; // t2.m_pointer指向nullptr
t2 = t1; // t2.m_pointer同样指向0xFF010203

t1.print();
t2.print();
// t2被释放,销毁0xFF010203内存空间
// t1被释放,再次销毁0xFF010203内存空间
return 0;
}

一般性原则:重载赋值操作符,必然需要实现深拷贝!

数组类的优化

//IntArray.h
#ifndef _INTARRAY_H_
#define _INTARRAY_H_

class IntArray {
private:
int m_length;
int* m_pointer;

IntArray(int len);
IntArray(const IntArray& obj);
bool construct();
public:
static IntArray* NewInstance(int length);
int length();
bool get(int index, int& value);
bool set(int index ,int value);
int& operator [] (int index);
IntArray& operator = (const IntArray& obj);
IntArray& self();
~IntArray();
};

#endif
//IntArray.cpp
#include "IntArray.h"

IntArray::IntArray(int len) {
m_length = len;
}

bool IntArray::construct() {
bool ret = true;
m_pointer = new int[m_length];
if( m_pointer ) {
for(int i=0; i<m_length; i++) {
m_pointer[i] = 0;
}
} else {
ret = false;
}
return ret;
}

IntArray* IntArray::NewInstance(int length) {
IntArray* ret = new IntArray(length);
if( !(ret && ret->construct()) ) {
delete ret;
ret = 0;
}
return ret;
}

int IntArray::length() {
return m_length;
}

bool IntArray::get(int index, int& value) {
bool ret = (0 <= index) && (index < length());
if( ret ) {
value = m_pointer[index];
}
return ret;
}

bool IntArray::set(int index, int value) {
bool ret = (0 <= index) && (index < length());
if( ret ) {
m_pointer[index] = value;
}
return ret;
}

int& IntArray::operator [] (int index) {
return m_pointer[index];
}

IntArray& IntArray::operator = (const IntArray& obj) {
if( this != &obj ) {
int* pointer = new int[obj.m_length];
if( pointer ) {
for(int i=0; i<obj.m_length; i++) {
pointer[i] = obj.m_pointer[i];
}
m_length = obj.m_length;
delete[] m_pointer;
m_pointer = pointer;
}
}
return *this;
}

IntArray& IntArray::self() {
return *this;
}

IntArray::~IntArray() {
delete[]m_pointer;
}
//main.cpp
#include <iostream>
#include <string>
#include "IntArray.h"

using namespace std;

int main() {
IntArray* a = IntArray::NewInstance(5);
IntArray* b = IntArray::NewInstance(10);
if( a && b ) {
IntArray& array = a->self();
IntArray& brray = b->self();
cout << "array.length() = " << array.length() << endl;
cout << "brray.length() = " << brray.length() << endl;
array = brray;
cout << "array.length() = " << array.length() << endl;
cout << "brray.length() = " << brray.length() << endl;
}
delete a;
delete b;
return 0;
}

编译器默认提供的函数

class Test {};
// 等价于
class Test {
public:
Test();
Test(const Test&);
Test& opertor= (const Test&);
~Test();
}

关于string的疑问

下面的代码输出什么?为什么?

string s = "12345";
const char *p = s.c_str();
cout<<p<<endl;
s.append("abced"); // string内部可能会重新分配一块内存存储"12345abced",此时p成为了野指针
cout<<p<<endl;
//输出"12345"(或者未定义行为),而不是"12345adced"

string对象内部维护了一个指向数据的char*指针,这个指针可能在程序运行的过程中发生改变。

下面的程序输出什么?为什么?

const char *p = "12345";
string s = "";
s.reserve(10);
for(int i=0;i<5;i++) {
s[i] = p[i]; //不要使用 C 语言中的方式操作 C++ 中的字符串
}
if(!s.empty()){ //s为空,跳过下面的执行语句
cout<<s<<endl;
}

for循环操作后,string::m_cstr指向的内容发生了变化,但是string::m_length未发生变化。

小结

  • 在需要进行深拷贝的时候必须重载赋值操作符
  • 赋值操作符和拷贝构造函数有同等重要的意义
  • string类通过一个数据空间保存字符串数据
  • string类通过一个成员 变量保存当前字符串的长度
  • C++开发时尽量避开C语言中惯用的编程思想

37 智能指针分析

内存泄漏(臭名昭著的Bug)

  • 动态申请堆空间,用完后不归还
  • C++语言中没有垃圾回收的机制
  • 指针无法控制所指堆空间的生命周期

编程实验:内存泄漏

#include <iostream>
#include <string>

using namespace std;

class Test {
int i;
public:
Test(int i) {
this->i = i;
}
int value() {
return i;
}
~Test() { }
};

int main() {
for(int i=0; i<5; i++) {
Test* p = new Test(i);
cout << p->value() << endl;
}
return 0;
}

我们需要什么

需要一个特殊的指针;指针生命周期结束时主动释放堆空间;一片堆空间最多只能由一个指针标识;杜绝指针运算和指针比较。

解决方案

重载指针特征操作符(->*);只能通过类的成员函数重载;重载函数不能使用参数;只能定义一个重载函数。

编程实验:智能指针

#include <iostream>
#include <string>

using namespace std;

class Test {
int i;
public:
Test(int i) {
cout << "Test(int i)" << endl;
this->i = i;
}
int value() {
return i;
}
~Test() {
cout << "~Test()" << endl;
}
};

class Pointer {
Test* mp;
public:
Pointer(Test* p = NULL) {
mp = p;
}
Pointer(const Pointer& obj) {
mp = obj.mp;
const_cast<Pointer&>(obj).mp = NULL;
}
Pointer& operator = (const Pointer& obj) {
if( this != &obj ) {
delete mp;
mp = obj.mp;
const_cast<Pointer&>(obj).mp = NULL;
}
return *this;
}
Test* operator -> () {
return mp;
}
Test& operator * () {
return *mp;
}
bool isNull() {
return (mp == NULL);
}
~Pointer() {
delete mp;
}
};

int main() {
Pointer p1 = new Test(0);
cout << p1->value() << endl;
Pointer p2 = p1;
cout << p1.isNull() << endl;
cout << p2->value() << endl;
return 0;
}

智能指针的使用军规:只能用来指向堆空间中的对象或者变量

小结

  • 指针特征操作符(->*)可以被重载
  • 重载指针特征符能够使用对象代替指针
  • 智能指针只能用于指向堆空间的内存
  • 智能指针的意义在于最大程度的避免内存问题